/*                                                                                                                           
 * JFFS3 -- Journalling Flash File System, Version 3.                                                                        
 *                                                                                                                           
 * Copyright (C) 2006  Ferenc Havasi <havasi@inf.u-szeged.hu>,                                                               
 *                     Zoltan Sogor <weth@inf.u-szeged.hu>,                                                                  
 *                     Matyas Majzik <admin@newarcana.com>,                                                               
 *                     University of Szeged, Hungary                                                                         
 *
 * This software is distributed under the GNU GPL v2.
 * For licensing information, see the file 'LICENCE' in this directory.                                                             
 *                                                                                                                           
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sim_vfs.h"

static struct super_block *gl_sb = NULL;
static struct file_system_type *gl_fst = NULL;

struct inode *smkdir(struct inode *where, char *name)
    {
    struct dentry *new_dir = NULL;

    if (!gl_sb)
        {
        printf("FS is not mounted!\n");
        return(NULL);
        }

    if (where == NULL)
        {
        if (gl_sb->s_root && gl_sb->s_root->d_inode)
            {
            where = gl_sb->s_root->d_inode;
            }
        else
            {
            printf("Get NULL inode but can't get root inode\n");
            return(NULL);
            }
        }
    else if (!S_ISDIR(where->i_mode))
        {
        printf("\"where\" is not a directory inode!\n");
        return(NULL);
        }

    new_dir = d_alloc(where->i_parent, name);
    if (!new_dir)
        {
        printf("Can't allocate memory for dentry!\n");
        return(NULL);
        }

    if (where->i_op->mkdir)
        {
        int ret;

        ret = where->i_op->mkdir(where, new_dir, S_IFDIR);
        if (ret)
            {
            d_free(new_dir);
            return(NULL);
            }

        return(new_dir->d_inode);
        }
    else
        {
        printf("This inode(#%lu) hasn't got mkdir operation!\n", where->i_ino);
        d_free(new_dir);
        return(NULL);
        }
    }

int srmdir(struct inode *which)
    {
    struct dentry *dentry = NULL;
    struct inode *dir_i = NULL;

    if (which == NULL)
        return(-1);

    if (!S_ISDIR(which->i_mode))
        {
        printf("This inode isn't a directori inode!\n");
        return(-1);
        }

    dentry = which->i_parent;
    dir_i = dentry->d_parent->d_inode;

    if (dir_i->i_op && which->i_op->rmdir)
        {
        int ret;

        ret = dir_i->i_op->rmdir(dir_i, dentry);
        if (ret)
            {
            printf("rmdir was unsuccessful\n");
            return(-1);
            }

        d_free(dentry);
        destroy_inode(which);

        }
    else
        {
        printf("Target inode dosen't support rmdir\n");
        return(-1);
        }

    return(0);
    }

struct inode *stouch(struct inode *where, char *name)
    {
    struct dentry *new_file = NULL;

    if (!gl_sb)
        {
        printf("FS is not mounted!\n");
        return(NULL);
        }

    if (where == NULL)
        {
        if (gl_sb->s_root && gl_sb->s_root->d_inode)
            {
            where = gl_sb->s_root->d_inode;
            }
        else
            {
            printf("Get NULL inode but can't get root inode\n");
            return(NULL);
            }
        }
    else if (!S_ISDIR(where->i_mode))
        {
        printf("\"where\" is not a directory inode!\n");
        return(NULL);
        }

    new_file = d_alloc(where->i_parent, name);
    if (!new_file)
        {
        printf("Can't allocate memory for dentry!\n");
        return(NULL);
        }

    if (where->i_op && where->i_op->create)
        {
        int ret;

        ret = where->i_op->create(where, new_file, S_IFREG, NULL);
        if (ret)
            {
            d_free(new_file);
            return(NULL);
            }

        return(new_file->d_inode);
        }
    else
        {
        printf("This inode(#%lu) hasn't got create operation!\n", where->i_ino);
        d_free(new_file);
        return(NULL);
        }
    }

int sdelete(struct inode *which)
    {
    struct dentry *dentry = NULL;
    struct inode *dir_i = NULL;

    if (which == NULL)
        return(-1);

    if (!S_ISREG(which->i_mode))
        {
        printf("This inode isn't a regular file inode!\n");
        return(-1);
        }


    dentry = which->i_parent;
    dir_i = dentry->d_parent->d_inode;

    if (dir_i->i_op && dir_i->i_op->unlink)
        {
        int ret;

        ret = dir_i->i_op->unlink(dir_i, dentry);
        if (ret)
            {
            printf("unlink was unsuccessful\n");
            return(-1);
            }

        d_free(dentry);
        destroy_inode(which);

        }
    else
        {
        printf("Target inode dosen't support rmdir\n");
        return(-1);
        }

    return(0);
    }

int sread(struct inode *which, uint32_t page_nr, uint32_t *buf, uint32_t *retlen)
    {
    int ret = 0;

    if (which == NULL)
        return(-1);

    if (!S_ISREG(which->i_mode))
        {
        printf("This inode isn't a regular file inode!\n");
        return(-1);
        }

    if (which->i_op && which->i_op->read)
        {
        ret = which->i_op->read(which, page_nr, buf, retlen);
        }
    else
        {
        printf("This inode doesn't support read operation!\n");
        return(-1);
        }

    return(ret);
    }

int swrite(struct inode *which, uint64_t ofs, uint32_t len, const uint32_t *buf, uint32_t *retlen)
    {
    int ret = 0;

    if (which == NULL)
        return(-1);

    if (!S_ISREG(which->i_mode))
        {
        printf("This inode isn't a regular file inode!\n");
        return(-1);
        }

    if (which->i_op && which->i_op->write)
        {
        ret = which->i_op->write(which, ofs, len, buf, retlen);
        }
    else
        {
        printf("This inode doesn't support write operation!\n");
        return(-1);
        }

    return(ret);
    }

#if 0
int load_fs()
    {
    int ret;

    ret = init_jffs3_fs();

    return(ret);
    }
#endif

int smount(uint32_t flags)
    {
    if (gl_sb)
        {
        printf("FS already mounted !\n");
        return(-1);
        }

    if (!gl_fst)
        {
        printf("FS not loaded !\n");
        return(-1);
        }

    if (gl_fst->get_sb)
        {
        gl_sb = gl_fst->get_sb(gl_fst,0,NULL,NULL);
        if (!gl_sb)
            {
            printf("Can't get super_block\n");
            return(-1);
            }
        }
    else
        {
        printf("FS hasn't got get_sb function!\n");
        return(-1);
        }

    return(0);
    }

int sumount()
    {
    if (!gl_fst)
        {
        printf("FS not loaded !\n");
        return(-1);
        }

    if (!gl_sb)
        {
        printf("FS is not mounted\n");
        return(-1);
        }

    deactivate_super(gl_sb);
    gl_sb = NULL;

    return(0);
    }

#if 0
int sgc()
    {
    struct jffs3_sb_info *sbi;
    int ret,i;

    if (!gl_sb)
        {
        printf("FS is not mounted !\n");
        return(-1);
        }

    sbi = gl_sb->s_fs_info;

    for (i=0;i<sbi->region_nr;i++)
        {
        JFFS3_RMAP_PRN_RAW(sbi, i);
        }

    show_sb_info(sbi);

    ret = jffs3_gc_pass(sbi);

    for (i=0;i<sbi->region_nr;i++)
        {
        JFFS3_RMAP_PRN_RAW(sbi, i);
        }

    show_sb_info(sbi);

    return(0);
    }

int sdebug(int op)
    {
    struct jffs3_sb_info *sbi;
    int ret;

    if (!gl_sb && (op != JFFS3_DBG_OP_ALLOC))
        {
        printf("FS is not mounted !\n");
        return(-1);
        }



    switch (op)
        {
        case JFFS3_DBG_OP_SBI :
            sbi = gl_sb->s_fs_info;
            show_sb_info(sbi);
            break;

        case JFFS3_DBG_OP_RMAP :
            sbi = gl_sb->s_fs_info;
            show_rmap(sbi);
            break;

        case JFFS3_DBG_OP_FLUSH :
            sbi = gl_sb->s_fs_info;
            ret = jffs3_tnc_flush(sbi,JFFS3_TNC_FLUSH_AND_FULLCLEAR, JFFS3_TNC_NONE);
            if (ret)
                {
                jffs3_warn("Error flushing tree-node-cache\n");
                }
            break;

        case JFFS3_DBG_OP_RMAPFLUSH:
            sbi = gl_sb->s_fs_info;
            ret = jffs3_write_region_map(sbi);
            if (ret)
                {
                jffs3_warn("Write region map failed!\n");
                }
            break;

        case JFFS3_DBG_OP_SBIFLUSH:
            sbi = gl_sb->s_fs_info;
            sbi->root_addr = sbi->td->root;
            sbi->tree_depth = sbi->td->tree_depth;
            dbg_tnc2("Tree depth is %i\n", sbi->tree_depth);
            ret = jffs3_write_superblock(sbi);
            if (ret)
                {
                jffs3_warn("Write superblock failed!\n");
                }
            break;

        case JFFS3_DBG_OP_JRNLFLUSH:
            sbi = gl_sb->s_fs_info;
            jffs3_flush_wbuf(sbi, sbi->jrnl_wbuf, PAD_ACCOUNTING, WBUF_JOURNAL);
            break;

        case JFFS3_DBG_OP_ALLOC:
#ifdef PRINT_ALLOC
            alloc_printout();
#endif
            break;

        default:
            dbg("Unknown operation");
            break;
        }

    return(0);
    }
#endif

struct inode *alloc_inode(struct super_block *sb)
    {
    struct inode *inode;
    static struct inode_operations empty_iops;

    if (sb->s_op->alloc_inode)
        inode = sb->s_op->alloc_inode(sb);
    else
        {
        //printf("No allocator defined for inode in sb\n");
        inode = malloc(sizeof(struct inode));
        if (!inode)
            {
            printf("Can't allocte memory for inode!'n");
            return(NULL);
            }
        memset(inode, 0, sizeof(struct inode));
        }

    if (inode)
        {
        inode->i_sb = sb;
        inode->i_op = &empty_iops;
        }

    return(inode);
    }
struct inode *new_inode(struct super_block *sb)
    {
    static unsigned long last_ino;
    struct inode *inode;

    inode = alloc_inode(sb);

    if (inode)
        {
        inode->i_ino = ++last_ino;
        }

    return(inode);
    }

void destroy_inode(struct inode *inode)
    {
    if (!inode)
        {
        return;
        }

    if (inode->i_sb->s_op->destroy_inode)
        inode->i_sb->s_op->destroy_inode(inode);
    else
        {
        free(inode);
        }
    }

int register_filesystem(struct file_system_type *type)
    {
    if (gl_fst != NULL)
        {
        printf("Filesystem is already loaded!\n");
        return(1);
        }

    gl_fst = type;
    return(0);
    }

struct super_block *sget(struct file_system_type *type, int (*set)(struct super_block *,void *), void *data)
    {
    struct super_block *s;
    int ret;

    s = malloc(sizeof(struct super_block));
    if (!s)
        {
        printf("Can't allocate memory for super_block!\n");
        return(s);
        }

    ret = set(s, data);
    if (ret)
        {
        free(s);
        return(NULL);
        }

    s->s_type = type;

    return(s);
    }

void deactivate_super(struct super_block *sb)
    {
    sb->s_type->kill_sb(sb);

    free(sb);
    }

struct dentry *d_alloc(struct dentry *parent, const char *name)
    {
    struct dentry *dentry;
    uint32_t len;

    len = strlen(name);
    //printf("len = %u\n",len);

    dentry = malloc(sizeof(struct dentry));
    if (!dentry)
        {
        printf("Can't allocate memory for dentry!\n");
        return(NULL);
        }

    memset(dentry, 0, sizeof(struct dentry));

    dentry->d_name = malloc(len + 1);
    if (!dentry->d_name)
        {
        printf("Can't allocate memory for dentry->d_name!\n");
        free(dentry);
        return(NULL);
        }

    memcpy(dentry->d_name, name, len);
    dentry->d_name[len] = 0;
    dentry->d_namelen = len;

    if (parent)
        {
        dentry->d_parent = parent;
        dentry->d_sb = parent->d_sb;
        }

    return(dentry);
    }

void d_free(struct dentry *d)
    {
    if (!d)
        return;

    free(d->d_name);
    //d->d_inode->i_parent = NULL;
    free(d);
    d = NULL;
    }


void d_instantiate(struct dentry *entry, struct inode * inode)
    {
    if (inode)
        inode->i_parent = entry;
    entry->d_inode = inode;
    }

struct dentry *d_alloc_root(struct inode * root_inode)
    {
    struct dentry *res = NULL;

    if (root_inode)
        {

        res = d_alloc(NULL, "/");
        if (res)
            {
            res->d_sb = root_inode->i_sb;
            res->d_parent = res;
            d_instantiate(res, root_inode);
            }
        }

    return(res);
    }

struct inode *iget(struct super_block *sb, unsigned long ino)
    {
    struct inode *inode = NULL;

    if (ino != 1)
        {
        printf("iget supports only ino #1");
        return(NULL);
        }

    inode = alloc_inode(sb);
    if (!inode)
        {
        printf("iget can't allocate inode structure for inode (#%lu)", ino);
        return(NULL);
        }

    // HACK
    inode->i_ino = 1;

    if (sb->s_op->read_inode)
        {
        sb->s_op->read_inode(inode);
        }
    else
        {
        printf("sb->read_inode not implemented!!!'n");
        if (sb->s_op->destroy_inode)
            sb->s_op->destroy_inode(inode);
        return(NULL);
        }

    return(inode);
    }
